package com.spicesoft.clientobjects.core;

import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import com.spicesoft.clientobjects.BeanRegistry;

public class MethodUtils {

  public static BeanMetadata getBeanMetadata(Class<?> clazz) {
    List<PropertyMetadata> propertyList = new ArrayList<PropertyMetadata>();

    for (Method method : clazz.getMethods()) {
      if (isPropertyGetter(method)) {
        PropertyMetadata pm = new PropertyMetadata();
        Class<?> returnType = method.getReturnType();
        pm.setEnhanced(BeanRegistry.isRegistered(returnType));
        pm.setGetterMethod(method);
        
        // Extract property name
        String propertyName = extractPropertyNameFromGetter(method);
        pm.setPropertyName(propertyName);
        
        // class property is a java.lang.Object field, we should ignore it
        if ("class".equals(propertyName)) {
          continue;
        }

        // Extract setter method
        String setterName = setterNameForProperty(propertyName);
        Method setter;
        try {
          setter = clazz.getMethod(setterName, returnType);
          pm.setSetterMethod(setter);
        } catch (SecurityException e) {
          throw new RuntimeException("Unable to locate setter method for property " + propertyName
              + " due to security restrictions. See cause.", e);
        } catch (NoSuchMethodException e) {
          throw new RuntimeException("Failed to locate setter method for property " + propertyName, e);
        }
        
        // Determine if it is an indexed property
        try {
          pm.setIndexedGetterMethod(clazz.getMethod(method.getName(), int.class));
          try {
            pm.setIndexedSetterMethod(clazz.getMethod(setter.getName(), int.class, returnType.getComponentType()));
            pm.setEnhanced(BeanRegistry.isRegistered(returnType.getComponentType()));
            pm.setComponentType(returnType.getComponentType());
          } catch (NoSuchMethodException e) {
            throw new RuntimeException("Indexed getter method exists for property " + propertyName +
                " but indexed setter is missing.", e);
          }
          pm.setIndexed(true);
        } catch (SecurityException e) {
          throw new RuntimeException("Unable to locate setter method for property " + propertyName
              + " due to security restrictions. See cause.", e);
        } catch (NoSuchMethodException e) {
          pm.setIndexed(false);
        }

        propertyList.add(pm);
      }
    }

    return new BeanMetadata(propertyList);
  }

  private static String setterNameForProperty(String propertyName) {
    return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
  }

  private static boolean isPropertyGetter(Method method) {
    return method.getParameterTypes().length == 0
        && ((method.getName().startsWith("get") && method.getName().length() > 3) || (method.getName().startsWith("is") && method
            .getName().length() > 2));
  }

  public static boolean isPropertyChangeListenerRemover(Method method) {
    if (!method.getName().equals("removePropertyChangeListener")) {
      return false;
    }

    // There are 2 types of PropertyChangeListener removers:
    // public void removePropertyChangeListener(PropertyChangeListener listener)
    // public void removePropertyChangeListener(String propertyName,
    // PropertyChangeListener listener)
    Class<?>[] params = method.getParameterTypes();
    if (params == null) {
      return false;
    } else if (params.length == 1) {
      if (!PropertyChangeListener.class.equals(params[0])) {
        return false;
      }
    } else if (params.length == 2) {
      if (!String.class.equals(params[0]) || !PropertyChangeListener.class.equals(params[1])) {
        return false;
      }
    } else {
      return false;
    }

    return true;
  }

  public static boolean isPropertyChangeListenerAdder(Method method) {
    if (!method.getName().equals("addPropertyChangeListener")) {
      return false;
    }

    // There are 2 types of PropertyChangeListener adders:
    // public void addPropertyChangeListener(PropertyChangeListener listener)
    // public void addPropertyChangeListener(String propertyName,
    // PropertyChangeListener listener)
    Class<?>[] params = method.getParameterTypes();
    if (params == null) {
      return false;
    } else if (params.length == 1) {
      if (!PropertyChangeListener.class.equals(params[0])) {
        return false;
      }
    } else if (params.length == 2) {
      if (!String.class.equals(params[0]) || !PropertyChangeListener.class.equals(params[1])) {
        return false;
      }
    } else {
      return false;
    }

    return true;
  }

  private static String extractPropertyNameFromGetter(Method method) {
    if (method.getName().startsWith("get")) {
      return Character.toLowerCase(method.getName().charAt(3)) + method.getName().substring(4);
    } else {
      return Character.toLowerCase(method.getName().charAt(2)) + method.getName().substring(3);
    }
  }

}
